package org.dicr.util.net;

import java.io.*;

/** TODO: Comparable<Net> */

/**
 * Network.
 * 
 * @author Igor A Tarasov &lt;develop@dicr.org&gt;
 * @version 1.0
 */
public class Net implements Serializable, Cloneable {

	private static final long serialVersionUID = 1L;

	/**
	 * IP/Mask divider.
	 */
	public static final char DIV = '/';

	/** address */
	protected IP ip = null;

	/** mask */
	protected Mask mask = null;

	/**
	 * Constructor for internal usage. (withowt checks).
	 */
	protected Net() {
		super();
	}

	/**
	 * Constructor.
	 * 
	 * @param addr
	 */
	public Net(IP addr) {
		super();
		if (addr == null) throw new IllegalArgumentException("null ip");
		this.ip = addr;
		byte[] bytes = addr.toBytes();
		int nz = 0;
		while (nz < bytes.length && bytes[nz] != 0) {
			nz++;
		}
		this.mask = new Mask();
		this.mask.value = Mask.bits2mask((byte) (nz * 8));
	}

	/**
	 * Constructor.
	 * 
	 * @param addr
	 * @param aMask
	 * @throws IncorrectAddressException
	 */
	public Net(IP addr, Mask aMask) throws IncorrectAddressException {
		super();
		if (addr == null) throw new IllegalArgumentException("null ip");
		if (aMask == null) throw new IllegalArgumentException("null mask");
		Net.check(addr, aMask);
		this.ip = addr;
		this.mask = aMask;
	}

	/**
	 * Constructor.
	 * 
	 * @param value
	 * @throws IncorrectAddressException
	 */
	public Net(String value) throws IncorrectAddressException {
		super();
		if (value == null) throw new IllegalArgumentException("null value");
		int _ip = 0;
		int _mask = 0;
		String[] parts = value.split("\\" + DIV, 2);
		String[] decs = parts[0].split("\\" + IP.DIV, 4);
		try {
			for (int i = 0; i < decs.length; i++) {
				short dec = Short.parseShort(decs[i]);
				if (dec < 0 || dec > 255) throw new IncorrectAddressException("network: " + value);
				_ip = (_ip << 8) | dec;
			}
			// дополняем адрес отсутствующими нулями
			_ip <<= (32 - 8 * decs.length);
			if (parts.length > 1) {
				if (parts[1].indexOf(IP.DIV) > 0) _mask = IP.parse(parts[1]);
				else _mask = Mask.bits2mask(Byte.parseByte(parts[1]));
			} else {
				// рассчитываем маску по нулевым декадам
				_mask = 0xFFFFFFFF;
				while ((_ip & (~(_mask << 8))) == 0 && _mask != 0) {
					_mask <<= 8;
				}
			}
		} catch (IllegalArgumentException ex) {
			throw new IncorrectAddressException("network: '" + value + "'", ex);
		}
		IP newIP = new IP(_ip);
		Mask newMask = new Mask(_mask);
		Net.check(newIP, newMask);
		this.ip = newIP;
		this.mask = newMask;
	}

	/**
	 * Return address.
	 * 
	 * @return IP
	 */
	public IP getIp() {
		return this.ip;
	}

	/**
	 * Return mask
	 * 
	 * @return Mask
	 */
	public Mask getMask() {
		return this.mask;
	}

	/**
	 * Return broadcast address.
	 * 
	 * @return broadcast
	 */
	public IP getBroadcast() {
		return new IP(this.ip.toInteger() | ~this.mask.toInteger());
	}

	/**
	 * Check if address is included in this network.
	 * 
	 * @param addr
	 * @return true if include
	 */
	public boolean isInclude(IP addr) {
		if (addr == null) throw new IllegalArgumentException("null addr");
		return (addr.toInteger() & this.mask.toInteger()) == this.ip.toInteger();
	}

	/**
	 * Check if network is subnetwork of this network (included).
	 * 
	 * @param subnet
	 * @return true if include or equal
	 */
	public boolean isInclude(Net subnet) {
		if (subnet == null) throw new IllegalArgumentException("null subnet");
		if (this.mask == subnet.mask) return this.ip == subnet.ip;
		int thisbits = this.mask.getBitsCount();
		int netbits = subnet.mask.getBitsCount();
		if (thisbits > netbits) return false;
		return isInclude(subnet.ip);
	}

	/**
	 * Convert to string. Format: A.A.A.A/B
	 * 
	 * @return string
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(this.ip);
		if (!this.mask.equals(Mask.SINGLE)) sb.append(DIV).append(this.mask.getBitsCount());
		return sb.toString();
	}

	/**
	 * Convert to string. Format: A.A.A.A/M.M.M.M
	 * 
	 * @return string
	 */
	public String toWideString() {
		StringBuilder sb = new StringBuilder();
		sb.append(this.ip);
		sb.append(DIV);
		sb.append(this.mask);
		return sb.toString();
	}

	/**
	 * Check if mask is compatible with ip.
	 * 
	 * @param ip
	 * @param mask
	 * @throws IncorrectAddressException
	 */
	public static void check(IP ip, Mask mask) throws IncorrectAddressException {
		if (ip == null) throw new IllegalArgumentException("null ip");
		if (mask == null) throw new IllegalArgumentException("null mask");
		if ((ip.toInteger() & mask.toInteger()) != ip.toInteger()) throw new IncorrectAddressException("net: " + ip
				+ DIV + mask);
	}

	/**
	 * Clone.
	 * 
	 * @return copy
	 */
	@Override
	public Net clone() throws CloneNotSupportedException {
		Net net = (Net) super.clone();
		net.ip = this.ip.clone();
		net.mask = this.mask.clone();
		return net;
	}

	/**
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int PRIME = 31;
		int result = 1;
		result = PRIME * result + ((this.ip == null) ? 0 : this.ip.hashCode());
		result = PRIME * result + ((this.mask == null) ? 0 : this.mask.hashCode());
		return result;
	}

	/**
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) return true;
		if (obj == null) return false;
		if (getClass() != obj.getClass()) return false;
		final Net other = (Net) obj;
		if (this.ip == null) {
			if (other.ip != null) return false;
		} else if (!this.ip.equals(other.ip)) return false;
		if (this.mask == null) {
			if (other.mask != null) return false;
		} else if (!this.mask.equals(other.mask)) return false;
		return true;
	}
}
